#include "ftpserver.h"
/*
init_tcpserver:初始化一个TCP服务器
ip:服务器本身的ip地址字符串
port:服务器的端口号
返回值:
    成功返回创建好的服务器的套接字描述符
    失败返回-1
*/
int init_tcpserver(const char *ip, short port)
{
    // 1.创建一个socket通信端口(socket)
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1)
    {
        perror("create socket failed");
        exit(-1);
    }

    // 设置套接字选项
    int optval = 1;
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (void *)&optval, sizeof(optval));
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, (void *)&optval, sizeof(optval));

    // 2.为创建的sockfd绑定一个网络地址
    // 描述一个网络地址
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;   // 协议族
    saddr.sin_port = htons(port); // 端口号
    inet_aton(ip, &saddr.sin_addr);
    // saddr.sin_addr.s_addr = INADDR_ANY;  //表示获取一个本机的任意的IP地址
    //  绑定操作
    int r = bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr));
    if (-1 == r)
    {
        perror("bind failed");
        close(sockfd);
        exit(-1);
    }
    // 3.开启对套接字的监听(客户端就可以连接服务器)
    r = listen(sockfd, 1024);
    if (-1 == r)
    {
        perror("listen failed");
        close(sockfd);
        exit(-1);
    }
    printf("init server success!\n");

    return sockfd;
}

/*
读取客户端的数据失败的时候,关闭连接
*/
void handle_read_error(int connfd)
{
    printf("disconnect client \n");
    close(connfd);
    exit(0); // 客户端主动断开了连接
}

void handle_connection(int connfd)
{
    //读取客户端的请求
    unsigned char cmd[1024] = {0};
    int i = 0;
    while(1)
    {
        //初始化
        memset(cmd,0,1024);
        //读取客户端发送的命令（按照协议读取并解析）
        //找到包头0xc0
        int r;
        unsigned char ch;
        //读取包头
        do//让其可以读取最少两次
        {
            r = read(connfd,&ch,1);
            if(r <= 0)
            {
                handle_read_error(connfd);//客户端主动断开连接
            }
        }while(ch != 0xc0);
        
        //防止读到上一个数据包的包尾
        while(ch == 0xc0)
        {//read读取后会自动偏移相应字节
            r = read(connfd,&ch,1);//1=一个字节
            if(r <= 0)
            {
                handle_read_error(connfd);//客户端主动断开连接
            }
        }//循环结束了，ch坑定不是0xc0，二十数据包中的第一个有效字节
        //一次性的整个数据包读取到cmd中
        i = 0;
        while(ch != 0xc0)
        {
            cmd[i++] = ch;
            r = read(connfd,&ch,1);
            if(r <= 0)
            {
                 handle_read_error(connfd);//客户端主动断开连接
            } 
        }
        //校验
        int pkg_len = cmd[0] | cmd[1] << 8 | cmd[2] << 16 | cmd[3] << 24;
        if(i != pkg_len)//校验包长度,实际读取到的字节数量和理论数量不一样时，直接丢弃
        {
            printf("pkg_len:%d,i:%d\n",pkg_len,i);
            continue;
        }
        //解析出当前的命令号，根据命令号去完成剩余的操作
        int cmd_no = cmd[4] | cmd[5] << 8 | cmd[6] << 16 | cmd[7] << 24;
        if(cmd_no == FTP_CMD_LS)
        {
            //处理客户端的ls请求
            resp_ls(connfd);

        }
        else if(cmd_no == FTP_CMD_GET)
        {
            //处理客户端的get请求
            char filename[256] = {0};
            int arglen = cmd[8] | cmd[9] << 8 | cmd[10] << 16 | cmd[11] << 24;
            for(int j = 0;j<arglen;j++)
            {
                filename[j] = cmd[12 + j];
            }
            //回复客户端的get请求
            resp_get(connfd,filename);
        }
        else if(cmd_no == FTP_CMD_PUT)
        {
            //处理客户端的put请求
            printf("recv resquest put\n");
            char filename[256] = {0};
            int arglen = cmd[8] | cmd[9] << 8 | cmd[10] << 16 | cmd[11] << 24;
            int j;
            for(j = 0;j<arglen;j++)
            {
                filename[j] = cmd[12+j];
            }
            //解析出客户端推送的大小
            int filesize = cmd[12+j] | cmd[13+j] << 8| cmd[14+j] << 16 | cmd[15+j] << 24;
            //处理客户端的推送文件
            resp_put(connfd,filename,filesize);
        }
        else if(cmd_no == FTP_CMD_BYE)
        {
            //处理客户端的bye请求
            handle_read_error(connfd);//客户端主动断开连接
        }
    }
}

//回复客户端的ls请求
void resp_ls(int connfd)
{
    
    //客户端向服务器发送ls请求
    //包头(1)	包长度(4)	 命令号(4) 回复长度（4）结果（1） 回复内容(文件名列表) 包尾(1)
    //在回复ls前，需要提前准备好文件名列表filenames
    unsigned char filename[4096] = {0};//保存服务器共享的目录中的文件名列表
    int result = 0;           // 获取文件名成功
    //打开文件名列表（打开目录/读取目录项）
    DIR *pdir = opendir(FTP_DIR);
    if(pdir == NULL)
    {
        perror("opendir failed");
        result = 1;//失败
    }
    else
    {
        //读取目录项
        struct dirent *pdirent = NULL;
        int cnt = 0;
        // 遍历目录条目
        while((pdirent = readdir(pdir)))
        {
            if(strcmp(pdirent->d_name,".") == 0 || strcmp(pdirent->d_name,"..") == 0)
            {// 跳过当前目录和父目录
                continue;
            }
            // 拼接文件名到filename，每个文件名后加换行符（方便客户端按行显示）
            cnt += sprintf(filename+cnt,"%s\n",pdirent->d_name);
        }
        closedir(pdir);
    }

    char resp[8192] = {0}; // 存储完整响应
    int i = 0;

    // 1. 包头（固定为0xc0）
    resp[i++] = 0xc0;

    // 2. 包长度（总字节数，不包含包头和包尾）
    int resplen = 1 + (result == 0 ? strlen(filename) : 4); // 回复长度：结果(1字节) + 内容（文件列表或错误码）
    int pkg_len = 4 + 4 + 4 + resplen; // 包长度=命令号(4)+回复长度(4)+结果(1)+内容长度
    // 按大端/小端字节序拆分4字节整数（此处为逐字节填充）
    resp[i++] = 0xff & pkg_len;// 存放pkg_len的第一个字节
    resp[i++] = 0xff & (pkg_len >> 8);
    resp[i++] = 0xff & (pkg_len >> 16);
    resp[i++] = 0xff & (pkg_len >> 24);

    // 3. 命令号（枚举值FTP_CMD_LS，标识ls命令）
    enum CMD_NO cmd_no = FTP_CMD_LS;
    resp[i++] = 0xff & cmd_no;// 存放cmd_no的第一个字节
    resp[i++] = 0xff & (cmd_no >> 8);
    resp[i++] = 0xff & (cmd_no >> 16);
    resp[i++] = 0xff & (cmd_no >> 24);

    // 4. 回复长度（resplen的值，即结果+内容的总长度）
    resp[i++] = 0xff & resplen;// 存放resplen的第一个字节
    resp[i++] = 0xff & (resplen >> 8);
    resp[i++] = 0xff & (resplen >> 16);
    resp[i++] = 0xff & (resplen >> 24);

    // 5. 结果（0成功，1失败）
    resp[i++] = result;

    // 6. 回复内容（根据result判断是文件列表还是错误码）
    if (result == 1) 
    {
        // 失败：填充错误码（errno，4字节）
        resp[i++] = 0xff & errno;// 存放errno的第一个字节
        resp[i++] = 0xff & (errno >> 8);
        resp[i++] = 0xff & (errno >> 16);
        resp[i++] = 0xff & (errno >> 24);
    } else 
    {
        // 成功：填充文件名列表（filename中的内容）
        for (int j = 0; j < resplen - 1; j++) 
        { // resplen-1是文件名列表长度（减去result的1字节）
            resp[i++] = filename[j];
        }
    }

    // 7. 包尾（固定为0xc0）
    resp[i++] = 0xc0;

    write(connfd, resp, i);
}

//回复客户端的请求
void resp_get(int connfd,const char *filename)
{
    printf("client request get file = %s\n",filename);

    // 包头(1)	包长度(4)	命令号(4)	回复长度(4)   结果(1)	 回复内容(文件的长度) 包尾(1)
    // 在回复get前,确认文件存在,并且需要获取文件大小
    int filesize = 0;
    int result = 0;
    unsigned char pathname[256] = {0};
    snprintf(pathname,256,"%s/%s",FTP_DIR,filename);
    struct stat st;
    int r = lstat(pathname,&st);
    if(r == -1)
    {
        result = 1;//告诉客户端，获取失败
    }
    else
    {
        if(S_ISREG(st.st_mode))
        {
            filesize = st.st_size;
        }
        else
        {
            errno = EISDIR;
            result = 1;
        }
    }

    char resp[20] = {0};//表示我要回复的具体的数据序列

    int resplen = 1 + 4;
    int pkg_len = 4 + 4 + 4 + resplen;
    enum CMD_NO cmd_no = FTP_CMD_GET;
    int i = 0;
    resp[i++] = 0xc0;//包头

     resp[i++] = 0xff & pkg_len; // 存放pkg_len的第一个字节
    resp[i++] = 0xff & (pkg_len >> 8);
    resp[i++] = 0xff & (pkg_len >> 16);
    resp[i++] = 0xff & (pkg_len >> 24);

    resp[i++] = 0xff & cmd_no; // 存放cmd_no的第一个字节
    resp[i++] = 0xff & (cmd_no >> 8);
    resp[i++] = 0xff & (cmd_no >> 16);
    resp[i++] = 0xff & (cmd_no >> 24);

    resp[i++] = 0xff & resplen; // 存放resplen的第一个字节
    resp[i++] = 0xff & (resplen >> 8);
    resp[i++] = 0xff & (resplen >> 16);
    resp[i++] = 0xff & (resplen >> 24);

    resp[i++] = result;
    if(result == 1)//失败就需要回复错误码
    {
        resp[i++] = 0xff & errno; // 存放errno的第一个字节
        resp[i++] = 0xff & (errno >> 8);
        resp[i++] = 0xff & (errno >> 16);
        resp[i++] = 0xff & (errno >> 24);
    }
    else//成功就需要回复文件大小
    {
        resp[i++] = 0xff & filesize; // 存放filesize的第一个字节
        resp[i++] = 0xff & (filesize >> 8);
        resp[i++] = 0xff & (filesize >> 16);
        resp[i++] = 0xff & (filesize >> 24);
    }
    resp[i++] = 0xc0;//包尾

    write(connfd,resp,i);
    //上面是发送了get的回复命令,告诉客户端,文件的大小

    if(result == 0)
    {
        int fd = open(pathname,O_RDONLY);
        if(fd == -1)
        {
            perror("in resp_get,open file error");
            return;
        }
        unsigned char buf[1024] = {0};
        while(1)
        {
            memset(buf,0,1024);
            size_t r = read(fd,buf,1024);
            if(r <= 0)
            {
                break;
            }
            write(connfd,buf,r);
        }
        close(fd);
    }
}

void resp_put(int connfd, const char *filename, int filesize)
{
    printf("client request put,filename = %s,filesize = %d\n",filename,filesize);

    unsigned char pathname[256] = {0};
    snprintf(pathname,256,"%s/%s",FTP_DIR,filename);

    int fd = open(pathname,O_RDWR | O_CREAT | O_TRUNC,0644);
    if(fd == -1)
    {
        perror("in resp_put open failed");
        return;
    }

    int cur_read = 0;
    while(cur_read < filesize)
    {
        unsigned char buf[1024] = {0};
        int r = read(connfd,buf,1024);
        if(r < 0)
        {
            printf("in resp_put read file failed\n");
            return;
        }
        else
        {
            cur_read += r;
            write(fd,buf,r);
        }
    }
    printf("client put  %s success!,filesize = %d\n", filename,cur_read);
    close(fd);
}




